home *** CD-ROM | disk | FTP | other *** search
- /*
- This file manages the asynchronous sounds used by the game.
- It demonstrates one of the simplest techniques of playing asynch sounds,
- using SndPlay with a callback. Notice this code does NOT depend on register a5
- for flagging that a sound is done, instead I stuff a pointer to the flag
- in the callback parameters. I am pointing this out because I think it is a much better
- way to do things, and the same technique can apply to any interrupt time code.
-
- This also manages each channel by using a channel priority. When a sound play request
- is received for a specific channel, if the sound has a higher priority it is played,
- otherwise it is ignored.
-
- This code is pretty re-usable, I have used it in a few other programs as well.
-
- Call InitSounds( filename ) to start it up. The file passed is a resource file
- that contains all the snds you need.
-
- Then use PlaySndAsynchChannel to play a sound, specifying the sound resource ID, the channel
- number, and the priorty. Right now this handles kNumChannels of sound.
-
- Call SoundKeeper() from your idle routine to make sure that the sound channels are
- disposed of when needed.
-
- Call FreeSounds before your program quits. Thats really the minimum you need in order
- to use this code. There is more to it, read the code and comments that follow.
-
-
-
- A point to mention here. There are so many different Macintosh sound managers out there,
- that it is hard to write good code that runs on all of them. This code SHOULD work on all
- of them. If not, please let me know. See the feedback forms.
-
- This code has two forks in it. If Sound Manager 3.0 is running, then it creates the
- sound channels at the start of the program, and keeps using them until the program quits.
- Earlier versions of the Sound Manager contained bugs, which caused intermittent failures
- unless a fresh sound channel was used for each asynchronous sound. So, if a sound manager
- earlier than 3.0 is installed, then this unit will create and dispose of sound channels
- on the fly.
-
- UNDER Sound Manager 3.0 on an AV macintosh with a DSP, it is important to open the channel
- once and keep using it until you are done. This is because every time the channel is opened
- (or closed) the disk may be hit as the DSP Synth code is loaded.
-
- Creating and disposing of a sound channel is not the most efficient way to play sounds, because the channel is created and
- disposed of for each sound, requiring the memory manager to scam a block from the heap.
-
- It would be better to open a channel, leave it open, and keep jamming bufferCmds in to
- play each sound instead. However - this does not work on all macs, like MacClassics, plusses
- and some others. If Sound Manager 3.0 is installed, then this code will work well.
-
- I have made this the simplest way of playing asynchronous sounds, but it is NOT the best for
- each particular Macintosh.
-
- Double buffering is good because it can use less RAM, but it is bad because if you are spooling
- the sound in then you are reading the disk or custom un-compressing, or otherwise doing
- some amount of processing to fill the buffer, and this can slow the program down, not to mention
- increase the complexity of any sound playing code.
-
- */
-
- #include "GameSounds.h"
-
- #include "ZAMProtos.h"
-
- unsigned char gSoundMgrVersion;
- SndChanInfo gChan[kNumChan];
- short gSndResFile;
- Boolean gSoundEnable;
-
- void SoundEnable(void);
-
- void InitSounds(Str255 sndFileName)
- /*
- open up the sound resource file, and preserve the resref num and stuff
-
- */
- {
- short i;
- OSErr err;
- short prevResFile;
-
-
- SoundEnable();
-
- /* get the version number of the sound manager */
- gSoundMgrVersion = SndSoundManagerVersion().majorRev;
-
- for(i = 0; i < kNumChan; i++) {
- gChan[i].channel = nil;
- gChan[i].priority = 0;
-
- if(gSoundMgrVersion >= 3) {
- /* we can pre-allocate the channels and leave them open till we quit */
- err = SndNewChannel(&gChan[i].channel, sampledSynth,
- initMono + initNoDrop + initNoInterp, SndDoneProc);
- if(err) {
- ErrMsgCode("\pError opening sound channel");
- ExitToShell();
- }
- }
- }
-
- /* now open the file with our sounds in it */
- prevResFile = CurResFile();
- gSndResFile = OpenResFile(sndFileName);
- if(gSndResFile == -1) {
- ErrMsg("\pError opening sound resource file");
- }
-
- /* remove the sound file from chain */
- UseResFile(prevResFile);
- }
-
-
- void SoundDisable(void)
- /*
- turn sounds off
-
- */
- {
- gSoundEnable = false;
- }
-
- void SoundEnable(void)
- /*
- turn sounds on
-
- */
- {
- gSoundEnable = true;
- }
-
- void StopAllSounds(void)
- /*
- Stop all sounds from playing by dumping them
- */
- {
- short i;
-
- for(i = 0; i < kNumChan; i++) {
- if(gChan[i].priority)
- SndDisposeChannel (gChan[i].channel, true);
- }
- }
-
-
- Handle GetSound(short sndID)
- /*
- Use this to get sounds from the file
- This makes the sound res file current again
- and grabs the sound.
-
- Inside Mac is ambiguous about locking down sound handles.
-
- They MUST be locked down before passed to sound play.
- */
- {
- short saveResFile;
- Handle snd;
-
- saveResFile = CurResFile();
-
- UseResFile(gSndResFile);
- snd = Get1Resource('snd ',sndID);
- HLock(snd);
- UseResFile(saveResFile);
-
- return snd;
- }
-
- OSErr PlaySndAsynchChannel(short sndID, short chanNum, short priority)
- /*
- This is the main sampled sound playing routine. See comments above.
-
- */
- {
- OSErr err = noErr;
- SndCommand cmd;
- Handle snd;
-
- if (!gSoundEnable) return;
-
- snd = GetSound(sndID);
- if(snd != nil) {
- if( (gChan[chanNum].priority != 0) && (priority >= gChan[chanNum].priority) ) {
- /* if we got the sound and the channel is busy, and our priority is high enough */
- /* then we are going to play, otherwise we bail */
- if(gChan[chanNum].priority) {
- if(gSoundMgrVersion < 0x03) {
- SndDisposeChannel (gChan[chanNum].channel, true);
- } else {
- /* since the good sound manager is around, stop playing the sound */
- cmd.cmd = quietCmd;
- cmd.param1 = 0;
- cmd.param2 = 0;
- err = SndDoImmediate(gChan[chanNum].channel, &cmd);
- cmd.cmd = flushCmd;
- err = SndDoImmediate(gChan[chanNum].channel, &cmd);
- }
- }
- }
-
- if(priority >= gChan[chanNum].priority) {
- if( gSoundMgrVersion < 0x03) {
- /* old sound manager around, so create a new sound channel */
- gChan[chanNum].channel = nil;
- err = SndNewChannel(&gChan[chanNum].channel, sampledSynth, initMono + initNoDrop + initNoInterp, SndDoneProc);
- }
- if(err == noErr) {
- err = SndPlay (gChan[chanNum].channel, snd, true);
- if (err == noErr) {
- gChan[chanNum].sndHandle = snd;
- gChan[chanNum].priority = priority;
- cmd.cmd = callBackCmd;
- cmd.param2 = (long)&gChan[chanNum];
- err = SndDoCommand (gChan[chanNum].channel, &cmd, false);
- }
- }
- }
- }
-
- return err;
- }
-
-
- OSErr PlaySndAsynchChannelNow(short sndID, short chanNum, short priority)
- /*
- The same as above, except does not check if sounds are enabled.
- This was a hacky way to make sure that you could hear the sorry bub you loose sound.
- */
- {
- OSErr err = noErr;
- SndCommand cmd;
- Handle snd;
-
- snd = GetSound(sndID);
- if(snd != nil) {
- if( (gChan[chanNum].priority != 0) && (priority >= gChan[chanNum].priority) ) {
- /* if we got the sound and the channel is busy, and our priority is high enough */
- /* then we are going to play, otherwise we bail */
- if(gChan[chanNum].priority) {
- if(gSoundMgrVersion < 0x03) {
- SndDisposeChannel (gChan[chanNum].channel, true);
- } else {
- /* since the good sound manager is around, stop playing the sound */
- cmd.cmd = quietCmd;
- cmd.param1 = 0;
- cmd.param2 = 0;
- err = SndDoImmediate(gChan[chanNum].channel, &cmd);
- cmd.cmd = flushCmd;
- err = SndDoImmediate(gChan[chanNum].channel, &cmd);
- }
- }
- }
-
- if(priority >= gChan[chanNum].priority) {
- if( gSoundMgrVersion < 0x03) {
- /* old sound manager around, so create a new sound channel */
- gChan[chanNum].channel = nil;
- err = SndNewChannel(&gChan[chanNum].channel, sampledSynth, initMono + initNoDrop + initNoInterp, SndDoneProc);
- }
- if(err == noErr) {
- err = SndPlay (gChan[chanNum].channel, snd, true);
- if (err == noErr) {
- gChan[chanNum].sndHandle = snd;
- gChan[chanNum].priority = priority;
- cmd.cmd = callBackCmd;
- cmd.param2 = (long)&gChan[chanNum];
- err = SndDoCommand (gChan[chanNum].channel, &cmd, false);
- }
- }
- }
- }
-
- return err;
- }
-
- void SoundKeeper(void)
- /*
- This routine must be called from your idle loop.
- It updates the sound channels, getting rid of them and setting the flags correctly.
- */
- {
- short i;
-
- for(i = 0; i < kNumChan; i++) {
- if(gChan[i].priority == -1) {
- gChan[i].priority = 0;
- if(gChan[i].sndHandle)
- HUnlock(gChan[i].sndHandle); /* do you really want to have a big old unlocked block?*/
-
- if(gSoundMgrVersion < 0x03) {
- SndDisposeChannel (gChan[i].channel, true);
- }
-
- }
- }
- }
-
- pascal void SndDoneProc(SndChannelPtr channel, SndCommand *cmd)
- /*
- This is the magical callback routine that flags SoundKeeper to dump this channel.
- It is magic because it does not use register a5, like most people suggest.
- I think it is lame to use register a5 to access a global from routines like this.
- It is much better to just store a pointer to the data you need!
- */
- {
- SndChanInfo *sndChan;
-
- sndChan = (SndChanInfo*)cmd->param2;
- sndChan->priority = -1;
- }
-
- void FreeSounds(void)
- /*
- Disposes of all the currently allocated sound channels
- and stops all sounds from playing.
- Also closes the sound file
- */
- {
- short i;
-
- for(i = 0; i < kNumChan; i++) {
- SndDisposeChannel (gChan[i].channel, true);
- gChan[i].priority = 0;
- }
-
- if(gSndResFile != -1) {
- CloseResFile(gSndResFile);
- }
- }